#pragma once
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <bits/stl_pair.h>
#include <sys/time.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>


namespace runtime {namespace cuda {

#if 0
#define CHECK_CUDA_ERR
#define CHECK_CUBLAS_ERR(condition)
#define TINIT
#define TIC
#define TOC(msg)
#define CHECK_CUDNN_ERR(f)
#else
#define CHECK_CUDA_ERR(call)\
do {\
  const cudaError_t error=call;\
  if(error!=cudaSuccess)\
  {\
      printf("ERROR: %s:%d,",__FILE__,__LINE__);\
      printf("code:%d,reason:%s\n",error,cudaGetErrorString(error));\
      exit(1);\
  }\
} while (0)
#define CHECK_CUBLAS_ERR(condition) \
  do { \
    cublasStatus_t error = condition; \
    if (CUBLAS_STATUS_SUCCESS != error) { \
      printf("code:%d,reason:%s\n",error, cublasGetErrorString(error));\
    } \
  } while (0)
#define TINIT         timeval t_start, t_end;
#define TIC           gettimeofday(&t_start, nullptr);
#define TOC(msg)      gettimeofday(&t_end, nullptr);  \
    printf("%s: time cost %6.3f (ms)\n", #msg, (t_end.tv_sec - t_start.tv_sec) * 1000.f + (t_end.tv_usec - t_start.tv_usec) / 1000.f);
#define CHECK_CUDNN_ERR(f) do { \
  cudnnStatus_t err = (f); \
  if (err != CUDNN_STATUS_SUCCESS) { \
    std::cout << "    Error occurred: " << err << std::endl; \
    std::exit(1); \
  } \
} while(0)
#endif

void initCuBlasHandle();
const char* cublasGetErrorString(cublasStatus_t error);

template <typename T>
void printMatrix(T* C,const int ncol,const int nrow) {
  T *ic=C;
  std::cout << "Matrix<" << nrow << " x " << ncol  << ">:" << std::endl;
  for(int i=0;i<nrow;i++) {
    for(int j=0;j<ncol;j++) {
      std::cout << ic[j] << " ";
    }
    ic+=ncol;
    std::cout << std::endl;
  }
}

template <typename T>
void read_from_bin(T* dst, const int elementSize, const std::string fileName) {
  if (!dst) {
    throw "Invalid addr!";
  }
  std::ifstream fin(fileName, std::ios::in | std::ios::binary);
  fin.read((char*)dst, elementSize * sizeof(T));
  fin.close();
  std::cout << "read " << elementSize * sizeof(T) << " Bytes from " << fileName << std::endl;
}

template <typename T>
void write_to_bin(const T* src, const int elementSize, const std::string fileName) {
  if (!src) {
    throw "Invalid addr!";
  }
  std::ofstream fout(fileName, std::ios::out | std::ios::binary);
  fout.write((const char*)src, elementSize * sizeof(T));
  fout.close();
  std::cout << "write " << elementSize * sizeof(T) << " Bytes to " << fileName << std::endl;
}

template <typename T>
std::pair<T, int64_t> compare_two_buff(const T* lhs, const T* rhs, const int elementSize, const float tolerence = 1e-5) {
  if (!lhs || !rhs) {
    throw "Invalid addr!";
  }
  T max_diff = (T)-1.0e10;
  int64_t max_diff_idx = -1;
  for (size_t i = 0; i < elementSize; i++) {
    if (abs(lhs[i] - rhs[i]) > max_diff) {
      max_diff = abs(lhs[i] - rhs[i]);
      max_diff_idx = i;
    }
  }
  return std::make_pair(max_diff, max_diff_idx);
}

class GpuMemManager
{
public:
  GpuMemManager(/* args */) = default;
  void* mapCpu2Gpu(void* cpuAddr, const int64_t byteSize);
  void mapGpu2Cpu(void* cpuAddr, void* gpuAddr, const int64_t byteSize);
  ~GpuMemManager();
private:
  std::vector<void*> m_AddrTable;
};


}} // end of namespace runtime/cuda
